/*
 * This file is part of Ember.
 *
 * Ember is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * Ember is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with Ember.  If not, see <http://www.gnu.org/licenses/>.
 *
 * Copyright (C) Grant Tyra 2010
 * granttyra@gmail.com
*/

#include "ember_class.h"
#include "ember_statement.h"
#include "ember_object.h"
#include "ember_string.h"
#include <string.h>
#include <stdlib.h>

EmberDictionary *emberClassMap = NULL;

void EmberClassMapInitialize() {
    emberClassMap = EmberDictionaryCreate();
}

void EmberClassMapDestroy() {
    EmberList *values = EmberListCreate();
    EmberClassDef *classdef = NULL;
    int i = 0;

	EmberDictionaryValues(emberClassMap, values);
    
    for (i = 0; i < values->size; i++) {
        classdef = EmberListGet(values, i);
        EmberClassDestroy(classdef);
    }
    
    EmberListDestroy(values);
    EmberDictionaryDestroy(emberClassMap);
    emberClassMap = NULL;
}

void EmberClassMapPut(EmberClassDef *classdef) {
	EmberDictionaryPut(emberClassMap, classdef->name, classdef);
}

EmberClassDef *EmberClassMapGet(const char *name) {
	return EmberDictionaryGet(emberClassMap, name);
}

EmberClassDef *EmberClassCreate(const char *name, const char *parent_name) {
	EmberClassDef *classdef = malloc(sizeof(EmberClassDef));
    classdef->name = EmberStringCopy(name);
    classdef->classMethods = EmberDictionaryCreate();
	classdef->instMethods = EmberDictionaryCreate();
	classdef->attributes = EmberDictionaryCreate();

    if (parent_name != NULL) {
        classdef->parent = EmberClassMapGet(parent_name);
    } else {
        classdef->parent = EmberClassMapGet("Object");
    }

	return classdef;
}

void EmberClassDestroy(EmberClassDef *classdef) {
	int i = 0;
	EmberList *classMethods = EmberListCreate();
	EmberList *instMethods = EmberListCreate();
	EmberList *attributes = EmberListCreate();
	EmberMethod *method = NULL;
	EmberObject *attribute = NULL;

	EmberDictionaryValues(classdef->classMethods, classMethods);
	EmberDictionaryValues(classdef->instMethods, instMethods);
	EmberDictionaryValues(classdef->attributes, attributes);

	for (i = 0; i < classMethods->size; i++) {
		method = EmberListGet(classMethods, i);
		EmberMethodDestroy(method);
	}

	for (i = 0; i < instMethods->size; i++) {
		method = EmberListGet(instMethods, i);
		EmberMethodDestroy(method);
	}

	for (i = 0; i < attributes->size; i++) {
		attribute = EmberListGet(attributes, i);
		EmberObjectRelease(attribute);
	}

	EmberListDestroy(classMethods);
	EmberListDestroy(instMethods);

	EmberDictionaryDestroy(classdef->classMethods);
	EmberDictionaryDestroy(classdef->instMethods);
	EmberStringDestroy(classdef->name);
	free(classdef);
}

char *method_build(EmberMethod **method, EmberList *lines, int start, int end, int *past, char *method_name) {
	char *line = EmberListGet(lines, start);
	char *arglist = EmberStringBetween(line, '(', ')');
	char *error = NULL;
	EmberStatement *statement = NULL;

	error = statement_build_root(&statement, lines, start + 1, end, past);

	if (error != NULL) {
		return error;
	}

	*method = EmberMethodCreate(arglist, 0, statement);
	EmberStringDestroy(arglist);

	return NULL;
}

void add_property(EmberClassDef *classdef, const char *mname, const char *aname) {
    EmberMethod *setter = NULL;
	EmberMethod *getter = NULL;
    char *setter_name = NULL;
	char *getter_name = NULL;
	char *setter_line = NULL;
	char *getter_line = NULL;
    EmberStatement *set_statement = NULL;
	EmberStatement *get_statement = NULL;

    setter_line = EmberStringCopy("@");
    EmberStringAppend(&setter_line, aname);
    EmberStringAppend(&setter_line, " = arg");

    getter_line = EmberStringCopy("return @");
    EmberStringAppend(&getter_line, aname);

    statement_build_single(&set_statement, setter_line);
    statement_build_single(&get_statement, getter_line);

    setter_name = EmberStringConcat("set", mname);
    getter_name = EmberStringConcat("get", mname);

    setter = EmberMethodCreate("arg", 0, set_statement);
    getter = EmberMethodCreate("", 0, get_statement);

	EmberClassAddInstanceMethod(classdef, setter_name, setter);
	EmberClassAddInstanceMethod(classdef, getter_name, getter);

    EmberStringDestroy(setter_name);
    EmberStringDestroy(getter_name);
    EmberStringDestroy(getter_line);
    EmberStringDestroy(setter_line);
}

char *EmberClassBuild(EmberClassDef **classdef, EmberList *lines, int begin, int count, int *past) {
	int i = 0;
	EmberMethod *method = NULL;
	char *class_name = NULL;
	char *parent_name = NULL;
	char *class_statement = NULL;
	char *method_name = NULL;
	char *method_sig = NULL;
	char *declaration = NULL;
	char *line = NULL;
	char *property_name = NULL;
	char *attribute_name = NULL;
	char *property_line = NULL;
	char *error = NULL;
	EmberList *parts = NULL;

	declaration = EmberListGet(lines, begin);
    class_statement = EmberStringAfterString(declaration, "class ");

    if (EmberStringContainsString(class_statement, " extends ")) {
        class_name = EmberStringBeforeString(class_statement, " extends");
        parent_name = EmberStringAfterString(class_statement, "extends ");
    } else {
        class_name = EmberStringCopy(class_statement);
        parent_name = NULL;
    }

    EmberStringTrim(&class_name);
    EmberStringTrim(&parent_name);

	*classdef = EmberClassCreate(class_name, parent_name);

	for (i = begin; i < count; i++) {
		line = EmberListGet(lines, i);

		if (line == NULL) {
			continue;
		} else if (EmberStringStartsWithString(line, "property ")) {
           property_line = EmberStringAfterString(line, "property ");
		   parts = EmberListCreate();
		   EmberStringSplit(property_line, ' ', parts);
		   property_name = EmberListGet(parts, 0);
		   attribute_name = EmberListGet(parts, 1);
           add_property(*classdef, property_name, attribute_name);
           EmberStringDestroy(property_name);
		   EmberStringDestroy(attribute_name);
		   EmberListDestroy(parts);
        } else if (EmberStringStartsWithString(line, "static")) {
			method_sig = EmberStringAfterString(line, "static ");
			method_name = EmberStringBefore(method_sig, '(');
			error = method_build(&method, lines, i, count, &i, method_name);
			EmberClassAddStaticMethod(*classdef, method_name, method);
			EmberStringDestroy(method_sig);
			EmberStringDestroy(method_name);
		} else if (EmberStringStartsWithString(line, "instance")) {
			method_sig = EmberStringAfterString(line, "instance ");
			method_name = EmberStringBefore(method_sig, '(');
			error = method_build(&method, lines, i, count, &i, method_name);
			EmberClassAddInstanceMethod(*classdef, method_name, method);
			EmberStringDestroy(method_sig);
			EmberStringDestroy(method_name);
		} else if (EmberStringStartsWithString(line, "end")) {
			break;
		}

		if (error != NULL) {
			return error;
		}
    }

	EmberStringDestroy(class_name);
    EmberStringDestroy(parent_name);
    EmberStringDestroy(class_statement);

	*past = i;

	return NULL;
}

EmberMethod *EmberMethodCreate(const char *arglist, int is_c, void *function) {
	EmberMethod *method = NULL;
	char *argument = NULL;
    EmberList *args = NULL;
	int i = 0;

	method = malloc(sizeof(EmberMethod));
    method->isNative = is_c;
    method->arguments = EmberListCreate();
    method->function = function;

	if (arglist != NULL) {
        args = EmberListCreate();
		EmberStringSplit(arglist, ',', args);

		if (args != NULL) {
			for (i = 0; i < args->size; i++) {
				argument = EmberListGet(args, i);
				EmberStringTrim(&argument);
				EmberListAdd(method->arguments, argument);
			}

			EmberListDestroy(args);
		}
	}

	return method;
}

void EmberMethodDestroy(EmberMethod *method) {
	int i = 0;
	char *argument = NULL;

	for (i = 0; i < method->arguments->size; i++) {
		argument = EmberListGet(method->arguments, i);
		EmberStringDestroy(argument);
	}

	if (!method->isNative) {
		statement_destroy(method->function);
	}

	EmberListDestroy(method->arguments);
	free(method);
}

void EmberClassMapValues(EmberList *values) {
	EmberDictionaryValues(emberClassMap, values);
}

EmberMethod *EmberClassGetInstanceMethod(EmberClassDef *classdef, const char *method_name) {
    EmberMethod *method = EmberDictionaryGet(classdef->instMethods, method_name);

    if (method == NULL && classdef->parent != NULL) {
        return EmberClassGetInstanceMethod(classdef->parent, method_name);
    }

    return method;
}

EmberMethod *EmberClassGetStaticMethod(EmberClassDef *classdef, const char *method_name) {
    EmberMethod *method = EmberDictionaryGet(classdef->classMethods, method_name);

    if (method == NULL && classdef->parent != NULL) {
        return EmberClassGetStaticMethod(classdef->parent, method_name);
    }

    return method;
}

void *EmberClassGetAttribute(EmberClassDef *classdef, const char *name) {
    EmberObject * obj = EmberDictionaryGet(classdef->attributes, name);

    if (obj == NULL && classdef->parent != NULL) {
        return EmberClassGetAttribute(classdef->parent, name);
    }

    return obj;
}

void EmberClassPutAttribute(EmberClassDef *classdef, const char *name, void *data) {
	EmberDictionaryPut(classdef->attributes, name, data);
}

void EmberClassAddStaticMethod(EmberClassDef *classdef, const char *name, EmberMethod *method) {
	EmberDictionaryPut(classdef->classMethods, name, method);
}

void EmberClassAddInstanceMethod(EmberClassDef *classdef, const char *name, EmberMethod *method) {
	EmberDictionaryPut(classdef->instMethods, name, method);
}
